// -*- C++ -*-

//========================================================================
/**
 * Author   : cuisw <shaovie@gmail.com>
 * Date     : 2009-05-29 02:24
 */
//========================================================================

#ifndef NDK_CONDITION_H_
#define NDK_CONDITION_H_

#include "ndk/ndk.h"
#include "ndk/time_value.h"

#if defined NDK_LINUX
#  include "ndk/thread.h"    // for thread::yield
#elif defined NDK_WIN32
#  include "ndk/thread_mutex.h"
#endif

namespace ndk
{
  /**
   * @class condition
   *
   * @brief 
   */
  template<typename mtx_t>
  class condition
  {
  public:
    inline condition(mtx_t &m)
      : mutex_(m)
#if defined NDK_WIN32
        , waiters_ (0)
#endif
    {
#if defined NDK_LINUX
      ::pthread_cond_init (&this->cond_, NULL);
#elif defined NDK_WIN32
      this->cond_ = ::CreateSemaphore(NULL, 0, LONG_MAX, NULL);
#endif
    }

    inline ~condition()
    {
#if defined NDK_LINUX
      int retry = 0;
      while ((::pthread_cond_destroy (&this->cond_) == EBUSY)
             && retry++ < 3)
      {
        this->broadcast ();
        ndk::thread::yield ();
      }
#elif defined NDK_WIN32
      CloseHandle(this->cond_);
#endif
    }

    inline int wait(void)
    {
#if defined NDK_LINUX
      int result = ::pthread_cond_wait (&this->cond_,
                                        const_cast<pthread_mutex_t*>(&this->mutex_.lock ()));
      if (result != 0)	{ errno = result; result = -1;}
      return result;
#elif defined NDK_WIN32
      return this->wait (0);
#endif
    }

    inline int wait(const ndk::time_value *time_out)
    {
#if defined NDK_LINUX
      if (time_out == 0)
        return this->wait();

      struct timespec ts;
      ts = ndk::gettimeofday () + *time_out;
      // shall not return an error code of [EINTR]
      int result = ::pthread_cond_timedwait (&this->cond_,
                                             const_cast<pthread_mutex_t*>(&this->mutex_.lock ()),
                                             &ts);
      if (result != 0) { errno = result; result = -1; }
      return result;
#elif defined NDK_WIN32
      DWORD wait_time = INFINITE;
      if (time_out != 0) wait_time = timeout->msec ();
      int result = 0;
      this->mutex_.release ();
      result = ::WaitForSingleObject (this->cond_, wait_time);
      if (result != WAIT_OBJECT_0) result = -1;
      this->mutex_.acquire ();
      return result;
#endif
    }

    inline int signal(void)
    {
#if defined NDK_LINUX
      return ::pthread_cond_signal (&this->cond_) == 0 ? 0 : -1;
#elif defined NDK_WIN32
      return ::ReleaseSemaphore(this->cond_, 1, NULL) ? 0 : -1;
#endif 
    }

    inline int broadcast(void)
    {
#if defined NDK_LINUX
      return ::pthread_cond_broadcast (&this->cond_) == 0 ? 0 : -1;
#elif defined NDK_WIN32
      this->waiter_mutex_.acquire ();
      int n = this->waiters_;
      this->waiter_mutex_.release ();
      if (n > 0) return ReleaseSemaphore(this->cond_, n, NULL) ? 0 : -1; 
      return 0;
#endif
    }

    inline mtx_t &mutex(void)
    {
      return this->mutex_;
    }
  private:
    mtx_t &mutex_;
#if defined NDK_LINUX 
    pthread_cond_t cond_;
#elif defined NDK_WIN32
    volatile int waiters_;
    ndk::thread_mutex waiter_mutex_;
    HANDLE cond_;
#endif
  };
} // namespace ndk

#endif // NDK_CONDITION_H_

